/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.objer.loader;

import java.io.File;
import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import org.zhiqim.git.GitI18n;
import org.zhiqim.git.GitZiper;
import org.zhiqim.git.objer.ObjectLoader;
import org.zhiqim.git.objer.objectid.RevObject;
import org.zhiqim.git.util.GitStreams;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.mutables.MInt;
import org.zhiqim.kernel.util.mutables.MLong;

/**
 * 松散对象加载器（一个文件即一个对象）
 * @see PackedObjectLoader  打包加载器
 *
 * @version v1.0.0 @author zouzhigang 2017-3-30 新建与整理
 */
public class UnpackedObjectLoader implements ObjectLoader
{
    private int type;
    private int size;
    private byte[] data;

    public UnpackedObjectLoader(File filePath) throws IOException
    {
        byte[] compressed = Files.read(filePath);
        Inflater inflater = GitZiper.inflater();
        
        try
        {
            if (isStandardFormat(compressed)){
                stdDecompress(inflater, compressed);
            }else{
                exDecompress(inflater, compressed);
            }
        }
        finally
        {
            GitZiper.close(inflater);
        }
    }
    
    /** 判断压缩风格 */
    private boolean isStandardFormat(byte[] hdr)
    {//通过前两个字节判断压缩格式是标准压缩(deflated)还是新风格，首字节为0x78,且两字节转为short能整除31认为是传统压缩，如0x78 0x9C
        int fb = hdr[0] & 0xff;
        return fb == 0x78 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0;
    }

    /** 标准压缩方式（standard zlib-deflated） */
    private void stdDecompress(Inflater inflater, byte[] compressed) throws IOException
    {
        inflater.setInput(compressed);
        final byte[] header = new byte[64];
        int avail = 0;
        while (!inflater.finished() && avail < header.length)
        {
            try
            {
                avail += inflater.inflate(header, avail, header.length - avail);
            }
            catch (DataFormatException e)
            {
                inflater.end();
                throw Asserts.exception(e);
            }
        }
        
        Asserts.asserts(avail >= 5, "对象文件没有头部信息");

        MInt off = new MInt();
        type = RevObject.type(Bytes.BU.getString(header, off, ' '));
        size = Ints.toInt(Bytes.BU.getString(header, off, '\0'));
        
        Asserts.asserts(size >= 0, "对象文件长度不能为负数");
        
        data = new byte[size];
        if (off.value < avail)
            System.arraycopy(header, off.value, data, 0, avail - off.value);
        
        decompress(inflater, avail - off.value);
    }
    
    /** 新风格压缩方式（experimental format） */
    private void exDecompress(Inflater inflater, byte[] compressed) throws IOException
    {
        MInt mType = new MInt();
        MLong mSize = new MLong();
        int off = GitStreams.readObjectHeader(compressed, 0, mType, mSize);

        switch (mType.value)
        {
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG:
            type = mType.value;
            break;
        default:
            throw Asserts.exception(GitI18n.illegalObjectType, mType.value);
        }

        size = (int)mSize.value;
        data = new byte[size];
        inflater.setInput(compressed, off, compressed.length - off);
        decompress(inflater, 0);
    }
    
    /** 解压 */
    private void decompress(Inflater inf, int p)
    {
        try
        {
            while (!inf.finished())
                p += inf.inflate(data, p, size - p);
        }
        catch (DataFormatException e)
        {
            throw Asserts.exception(e);
        }
        
        if (p != size)
            throw Asserts.exception("非法的解压长度");
    }
    
    /***********************************************************************************************/
    //必须支持的属性
    /***********************************************************************************************/
    
    @Override
    public int getType()
    {
        return type;
    }

    @Override
    public long getSize()
    {
        return size;
    }

    @Override
    public byte[] getData()
    {
        return data;
    }

    @Override
    public int getRawType()
    {
        return type;
    }

    @Override
    public long getRawSize()
    {
        return size;
    }
}
